In [8]:
SCOPETYPE = 'OPENADC'
PLATFORM = 'CWLITEXMEGA'
SS_VER = 'SS_VER_2_1'
%run "../../Setup_Scripts/Setup_Generic.ipynb"
INFO: Found ChipWhisperer😍 <class 'chipwhisperer.capture.api.programmers.XMEGAProgrammer'> scope.adc.offset changed from 73700 to 0 scope.adc.samples changed from 3000 to 5000 scope.clock.adc_freq changed from 86972785 to 30185691 scope.clock.adc_rate changed from 86972785.0 to 30185691.0
In [2]:
%%bash -s "$PLATFORM" "$SS_VER"
cd ../../../hardware/victims/firmware/simpleserial-des
make PLATFORM=$1 CRYPTO_TARGET=AVRCRYPTOLIB SS_VER=$2 -j
SS_VER set to SS_VER_2_1 SS_VER set to SS_VER_2_1 . Size after: +-------------------------------------------------------- avr-gcc (Homebrew AVR GCC 9.4.0) 9.4.0 Copyright (C) 2019 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Welcome to another exciting ChipWhisperer target build!! + Built for platform CW-Lite XMEGA with: text data bss dec hex filename 4652 8 82 4742 1286 simpleserial-des-CWLITEXMEGA.elf + CRYPTO_TARGET = AVRCRYPTOLIB + CRYPTO_OPTIONS = DES +--------------------------------------------------------
In [3]:
fw_path = f"/Users/alex/Documents/ChipWhisperer/chipwhisperer/hardware/victims/firmware/simpleserial-des/simpleserial-des-{PLATFORM}.hex"
print(fw_path)
prog = cw.programmers.XMEGAProgrammer
cw.program_target(scope, prog, fw_path)
/Users/alex/Documents/ChipWhisperer/chipwhisperer/hardware/victims/firmware/simpleserial-des/simpleserial-des-CWLITEXMEGA.hex XMEGA Programming flash... XMEGA Reading flash... Verified flash OK, 4659 bytes
We'll probably crash the target a few times while we're trying some glitching. Create a function to reset the target:
In [4]:
def reboot_flush():
scope.io.pdic = False
time.sleep(0.1)
scope.io.pdic = "high_z"
time.sleep(0.1)
#Flush garbage too
target.flush()
In [5]:
def print_output(returned_data):
print(returned_data)
print(f'Cmd: {chr(returned_data[1])}')
print(f'Len: {returned_data[2]}')
print(f'Payload (hex): {returned_data[3:-2].hex(" ",1).upper()}')
In [6]:
def capture_traces(cmd, data):
target.flush()
scope.arm()
target.send_cmd(cmd, 0, data)
capture = scope.capture()
if capture:
print("Timeout")
data = target.read_cmd('r')
trace = scope.get_last_trace()
return data, trace
In [9]:
from tqdm.notebook import trange
import numpy as np
# Create project to save traces to
project = cw.create_project("projects/des", overwrite = True)
reboot_flush()
# Display the actual key
target.send_cmd("x", 0, [])
key = target.read_cmd('r')
print_output(key)
ktp = cw.ktp.Basic()
ktp.text_len = 8
ktp.key_len = 8
# We want 3000 samples from two points
scope.adc.samples = 3000
# Round 1 is performed at offset 73700
# Round 2 is performed at offset 131700
# Your points may be different
offsets = [73700, 131700]
# 200 traces from each run
num_traces = 200
traces = []
for _ in trange(num_traces):
key, pt = ktp.next()
long_trace = np.array([])
# Get trace from two offsets and concatenate them
# so we don't consume/process other data
for i in range(len(offsets)):
scope.adc.offset = offsets[i]
data, trace = capture_traces('p',pt)
long_trace = np.append(long_trace, trace)
traces.append(cw.Trace(long_trace, pt, data[3:-2], key))
project.traces.extend(traces)
project.save()
CWbytearray(b'00 72 08 2b 7e 15 16 28 ae d2 a6 e3 00') Cmd: r Len: 8 Payload (hex): 2B 7E 15 16 28 AE D2 A6
0%| | 0/200 [00:00<?, ?it/s]
In [10]:
# Show entire trace
plot = cw.plot()
for i in range(14):
plot *= cw.plot(project.traces[i].wave)
plot
Out[10]:
In [11]:
# Due to slight timing variations, we need to ensure that our traces
# are synchronised. Do this here.
import chipwhisperer.analyzer as cwa
resync_traces = cwa.preprocessing.ResyncDTW(project)
resync_traces.ref_trace = 0
resync_traces.radius = 3
resync_analyzer = resync_traces.preprocess()
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 200/200 [00:02<00:00, 83.81it/s]
In [12]:
# And plot it
plt = cw.plot([])
for i in range(14):
plt *= cw.plot(resync_analyzer.waves[i])
plt
Out[12]:
In [18]:
# CPA for round 1
import warnings
import chipwhisperer.analyzer as cwa
from chipwhisperer.analyzer.attacks.cpa_algorithms.progressive import CPAProgressive
from chipwhisperer.analyzer.attacks.cpa_new import CPA
from chipwhisperer.analyzer.attacks.models.DES import DES, SBox_1_output
warnings.filterwarnings('ignore')
# Round 1 sbox operation starts at trace ~0
start_point= 0
# Results set
results = [[] for _ in range(2)]
leak_model = DES(model=SBox_1_output)
attack = cwa.cpa(resync_analyzer, leak_model)
attack.set_target_subkeys([0, 1, 2, 3, 4, 5, 6, 7])
attack.set_trace_source = attack.project.trace_manager()
attack.set_point_range((start_point,start_point+2200))
cb = cwa.get_jupyter_callback(attack, 8)
results[0] = attack.run(cb)
# Expected round 1 key - 22 10 30 21 32 38 07 3F
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
|---|---|---|---|---|---|---|---|---|
| PGE= | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 0 | 22 0.799 |
10 0.619 |
30 0.499 |
21 0.708 |
32 0.578 |
38 0.555 |
07 0.665 |
3F 0.601 |
| 1 | 18 0.474 |
14 0.434 |
33 0.408 |
09 0.512 |
37 0.411 |
01 0.537 |
0B 0.356 |
2C 0.326 |
| 2 | 15 0.464 |
12 0.366 |
34 0.403 |
19 0.489 |
01 0.388 |
08 0.516 |
05 0.347 |
21 0.323 |
| 3 | 29 0.460 |
19 0.364 |
3A 0.373 |
23 0.482 |
34 0.385 |
3C 0.516 |
26 0.333 |
22 0.316 |
| 4 | 20 0.426 |
1D 0.360 |
3D 0.362 |
31 0.480 |
22 0.364 |
07 0.494 |
01 0.324 |
0E 0.311 |
In [15]:
from textwrap import wrap
from chipwhisperer.analyzer.attacks.models.DES import DESLeakageHelper
# We now want to take the round 1 key from above,
# calculate hypothetical round 2 input
# and save it for running the round 2 attack
round_2 = cw.create_project("projects/des_round_2", overwrite = True)
des = DESLeakageHelper()
key = 0
for i, subkey in enumerate(results[0].best_guesses()):
key |= int(subkey["guess"]) << ((7-i) * 6)
key = f'{key:048b}'
# We'll take the synced traces project for input
for trace in resync_analyzer.traces:
pt = ''.join(f'{i:02X}' for i in trace[1])
input = des.hex_to_bin(pt)
# Get R1 output/R2 input
output, _ = des.get_round_1_output(input, key)
ct = []
binlist = wrap(output, 8)
for bin in binlist:
ct.append(int(bin,2))
new_trace = cw.Trace(trace[0],ct,[0]*8,[0]*8)
round_2.traces.append(new_trace)
# Save the project
round_2.save()
In [16]:
# CPA for round 2
import warnings
warnings.filterwarnings('ignore')
import chipwhisperer.analyzer as cwa
from chipwhisperer.analyzer.attacks.models.DES import SBox_2_output
start_point=3100
leak_model = DES(model=SBox_2_output)
attack = cwa.cpa(round_2, leak_model)
attack.set_target_subkeys([0, 1, 2, 3, 4, 5, 6, 7])
attack.set_trace_source = attack.project.trace_manager()
attack.set_point_range((start_point,start_point+2500))
cb = cwa.get_jupyter_callback(attack, 8)
results[1] = attack.run(cb)
# Expected round 2 key - 16 1A 01 0A 3D 1E 1F 2A
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
|---|---|---|---|---|---|---|---|---|
| PGE= | 18 | 25 | 44 | 14 | 47 | 14 | 38 | 27 |
| 0 | 16 0.830 |
1A 0.616 |
01 0.601 |
0A 0.604 |
3D 0.534 |
1E 0.612 |
1F 0.640 |
2A 0.639 |
| 1 | 1D 0.509 |
18 0.385 |
17 0.414 |
25 0.512 |
0B 0.377 |
01 0.402 |
04 0.390 |
2B 0.410 |
| 2 | 34 0.468 |
13 0.377 |
2E 0.383 |
1A 0.459 |
1B 0.345 |
23 0.374 |
18 0.356 |
05 0.346 |
| 3 | 14 0.467 |
1E 0.362 |
1C 0.378 |
22 0.441 |
14 0.324 |
0E 0.356 |
10 0.352 |
28 0.334 |
| 4 | 09 0.459 |
35 0.358 |
05 0.351 |
0E 0.428 |
38 0.320 |
3A 0.354 |
26 0.345 |
37 0.317 |
In [17]:
# Expected round keys:
#
# round 1 key - 22 10 30 21 32 38 07 3F
# round 2 key - 16 1A 01 0A 3D 1E 1F 2A
# Calculate the full 64-bit key (parity bits are not calculated)
des = DESLeakageHelper()
key = [[] for _ in range(2)]
binkey = ['' for _ in range(2)]
for i,k in enumerate(key):
print(f'Best guess key (R{i+1}): ', end='')
for subkey in results[i].best_guesses():
key[i].append(subkey["guess"])
print(f'{subkey["guess"]:0>2X}', end=' ')
print()
round_key = des.get_round_key(key[i],i+1,0, False)
for bit in round_key:
binkey[i] += str(bit).replace('?','0')
xor_round_keys = f'{int(binkey[0],2) | int(binkey[1],2):02X}'
wrap(xor_round_keys, 2)
# Expected key without parity bits set
# 2B 7E 14 16 28 AE D2 A6
Best guess key (R1): 22 10 30 21 32 38 07 3F Best guess key (R2): 16 1A 01 0A 3D 1E 1F 2A
Out[17]:
['2A', '7E', '14', '16', '28', 'AE', 'D2', 'A6']